package parser.util;

import imp.ocl.VariableImp;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import parser.astgen.Environment;
import parser.astgen.NamedElement;
import parser.astgen.NodeFactory;
import parser.astlib.OclFormalParameter;
import parser.astlib.OclTimeExp;
import parser.sablecc.analysis.AttrEvalException;
import project.Project;
import uml2.associationclasses.AssociationClass;
import uml2.kernel.Association;
import uml2.kernel.Classifier;
import uml2.kernel.Element;
import uml2.kernel.Generalization;
import uml2.kernel.LiteralInteger;
import uml2.kernel.Operation;
import uml2.kernel.Parameter;
import uml2.kernel.ParameterDirectionKindEnum;
import uml2.kernel.Property;
import uml2.kernel.Type;
import uml2.kernel.TypedElement;
import uml2.kernel.UmlClass;
import uml2.ocl2.AQualifierParentNav;
import uml2.ocl2.AssociationClassCallExp;
import uml2.ocl2.BagType;
import uml2.ocl2.CallExp;
import uml2.ocl2.CollectionType;
import uml2.ocl2.IterateExp;
import uml2.ocl2.IteratorExp;
import uml2.ocl2.OclExpression;
import uml2.ocl2.OperationCallExp;
import uml2.ocl2.OrderedSetType;
import uml2.ocl2.PropertyCallExp;
import uml2.ocl2.SequenceType;
import uml2.ocl2.SetType;
import uml2.ocl2.TupleLiteralExp;
import uml2.ocl2.TupleLiteralPart;
import uml2.ocl2.TupleType;
import uml2.ocl2.Variable;
import uml2.ocl2.VariableExp;
import facade.UserFacade;
import facade.ocl.AQualifierParentNavFacade;

public class Utility {
	
	UserFacade uf;
	Project p;
	NodeFactory factory;
	public static final boolean verbose = false;
	TypeCheck tc;
	
	// Constants for identify result type of PropertyCallExp in a navigation
	public final int SET_INDIVIDUAL = 0;
	public final int SET_MULTIPLE = 1;
	public final int BAG = 2;
	public final int CLASS = 3;
	public final int ASSOCIATIVE_CLASS = 4;
	public final int ATTRIBUTE = -2;
	public final int ERROR = -3;
	public final int ORDEREDSET = 5;
	public final int SEQUENCE = 6;
	
	public Utility (UserFacade uf, Project p) {
		this.uf = uf;
		this.p = p;
		this.factory = new NodeFactory(p);
	}
	
	public void setTypeCheck(TypeCheck tc) {
		this.tc=tc;
	}
	
	public Property findPropertyByNameAndClass(String propertyName, Classifier clas) throws AttrEvalException
	{
		Property property = null;
		boolean found = false;
		List properties = uf.getAllProperties();
		
		int i = 0;
		while (!found && i < properties.size())
		{
			Property prop = (Property)properties.get(i);
			if (prop.getName()!=null) {
				if (prop.getName().equals(propertyName)) 
				{
					if (prop.getUmlclass()!=null) {
						if (prop.getUmlclass().equals(clas)) {
							property = prop;
							found = true;
						}
					}
				}	
			}
			i++;
		}
		if (!found) {
			UmlClass superClass = this.getSuperClass((UmlClass) clas);
			if (superClass != null) property = findPropertyByNameAndClass(propertyName, superClass);
		}
		return property;
	}
	
	public Property findAttributeOfTupleTypeByName(TupleType tt, String name) {
		List attrs = tt.getOwnedAttribute();
		for (Iterator iterator = attrs.iterator(); iterator.hasNext();) {
			Property p = (Property) iterator.next();
			if (p.getName().equals(name)) return p;
		}
		return null;
	}
	
	public boolean propertyIsAssociationEnd(Property p) {
		if (p.getAssociation() != null) return true;
		else return false;
	}
	
	public boolean propertyIsOfClass(Property p, UmlClass u) throws AttrEvalException {
		Property prop = findPropertyByNameAndClass(p.getName(), u);
		if (prop!=null) {
			return true;
		}
		return false;
	}
	
	// Exists navigation Class A -> (p) Class B (Binary association without AssociationClass) 
	public boolean existsConnection(UmlClass source, Property p) throws AttrEvalException {
		
		if (source instanceof AssociationClass) return existsConnection((AssociationClass)source, p);
		// Support only binary associations without associationclass
		Association asc = p.getAssociation();
		//if (asc instanceof AssociationClass) return false;

		List members = asc.getMemberEnd();
		Property one, two;
		UmlClass superClass;
		
		if (members.size() != 2) return false;
		one = (Property) members.get(0);
		two = (Property) members.get(1);
		if (one.equals(p)) {
			if (two.getType().equals(source)) return true;
			else {
				superClass = this.getSuperClass(source);
				if (superClass == null) return false;
				else return existsConnection(superClass, p);
			}
		}
		if (two.equals(p)) {
			if (one.getType().equals(source)) return true;
			else {
				superClass = this.getSuperClass(source);
				if (superClass == null) return false;
				else return existsConnection(superClass, p);
			}
		}
		
		return false;
	}
	
	public UmlClass getSuperClass(UmlClass source) throws AttrEvalException {
		Collection fathers = source.getGeneralization();
		if (fathers.size()>1) throw new AttrEvalException("Problem with "+source.getName()+" class." +
				" Multiple hierarchy not supported");
		if (fathers.isEmpty()) return null;
		
		// Now, the correct case -> only one superclass
		return (UmlClass) ((Generalization) fathers.iterator().next()).getGeneral();
	}
	
	public Collection getSubClasses(UmlClass source) {
		Collection subhierarchy = source.getGeneralizationOfGeneral();
		Collection subClasses = new LinkedList();
		for (Iterator iterator = subhierarchy.iterator(); iterator.hasNext();) {
			Generalization subClassBranch = (Generalization) iterator.next();
			subClasses.add((UmlClass)subClassBranch.getSpecific());
		}
		return subClasses;
	}

	// Exists navigation AssociationClass ac -> Property p
	public boolean existsConnection(AssociationClass source, Property p) throws AttrEvalException {
		Association asc = p.getAssociation();
		if (asc.equals(source)) {
			return true;
		}
		
		Collection elements = asc.getMemberEnd();
		for (Iterator iterator = elements.iterator(); iterator.hasNext();) {
			Property pend = (Property) iterator.next();
			if (pend.getType().equals(source)) return true;
		}
		
		// if we are here, we must try it again with the superclass of source
		UmlClass superClass = this.getSuperClass(source);
		if (superClass == null) return false;
		else return existsConnection(superClass, p);
	}
	
	// Exists navigation Class -> AssociationClass
	public boolean existsConnection(UmlClass source, AssociationClass destiny) throws AttrEvalException {
		List members = destiny.getMemberEnd();
		for (Iterator iterator = members.iterator(); iterator.hasNext();) {
			Property associationEnd = (Property) iterator.next();
			if (associationEnd.getType().equals(source)) return true;
		}
		
		Collection elements = destiny.getTypedElementOfType();
		for (Iterator it2 = elements.iterator(); it2.hasNext();) {
			Element el = (Element) it2.next();
			if (el instanceof Property) {
				Property pend = (Property) el;
				if (this.propertyIsAssociationEnd(pend)) {
					Association as = pend.getAssociation();
					List memb = as.getMemberEnd();
					for (Iterator it3 = memb.iterator(); it3.hasNext();) {
						Property pend2 = (Property) it3.next();
						if (pend2.getType().equals(source)) return true;
					}
				}
			}
		}
		
		// if we are here, we must try it again with the superclass of source
		UmlClass superClass = this.getSuperClass(source);
		if (superClass == null) { 
			return false;
		}
		else {
			if (verbose) System.out.println("superclass:"+superClass.getName());
			return existsConnection (superClass, destiny);
		}
	}
	
	// ac associationclass is a member of the source associationclass
	public boolean existsConnectionBetweenAssociationClasses(AssociationClass source, AssociationClass ac) {
		List members = source.getMemberEnd();
		for (Iterator iterator = members.iterator(); iterator.hasNext();) {
			Property associationEnd = (Property) iterator.next();
			if (associationEnd.getType().equals(ac)) return true;
		}
		return false;
	}
	
	public Property findPropertyOfDestinyBetweenAssociationClasses(AssociationClass source, AssociationClass ac) {
		List members = source.getMemberEnd();
		for (Iterator iterator = members.iterator(); iterator.hasNext();) {
			Property associationEnd = (Property) iterator.next();
			if (associationEnd.getType().equals(ac)) return associationEnd;
		}
		return null;
	}
	
	// Exists navigation Class -> Class (Binary association without AssociationClass)
	public boolean existsConnection(UmlClass source, UmlClass destiny) throws AttrEvalException {
		Collection elementsWithDestinyType = destiny.getTypedElementOfType();
		for (Iterator element = elementsWithDestinyType.iterator(); element.hasNext();) {
			Element destinyElement = (Element) element.next();
			if (destinyElement instanceof Property) {
				if (this.existsConnection(source, (Property)destinyElement)) return true;
			}
		}
		
		// if we are here, we must try it again with the superclass of source
		UmlClass superClass = this.getSuperClass(source);
		if (superClass == null) return false;
		else return existsConnection (superClass, destiny);
	}
	
	// Find property (associationEnd) of class with connection to another class
	public Property findPropertyOfDestinyBySource(UmlClass source, UmlClass destiny) throws AttrEvalException {
		Collection members = destiny.getTypedElementOfType();
		for (Iterator iterator = members.iterator(); iterator.hasNext();) {
			Element element = (Element) iterator.next();
			if (element instanceof Property) {
				if (this.existsConnection(source, (Property)element)) return (Property) element;
			}
		}
		
		// if we are here, we must try it again with the superclass of source
		UmlClass superClass = this.getSuperClass(source);
		if (superClass == null) throw new AttrEvalException("Problem with the processor");
		else return findPropertyOfDestinyBySource(superClass, destiny);
	}
	
	// Search inside a list of properties, the one with the UmlClass type. This must be different than p 
	/*public Property searchProperty(UmlClass source, List members, Property p) {
		Property result;
		Iterator it = members.iterator();
		while(it.hasNext()){
			result = (Property)it.next();
			if (result.getType()!=null) {
				if (result.getType().equals(source) && !result.equals(p)) return result;
			}
		}
		return null;
	}*/
	
	public AssociationClass findAssociationClassByName(String name) throws AttrEvalException {
		
		// First, convert name into Name (upper case of the first character) in order to find the associationclass
		if (this.isFirstCharUpper(name)) throw new AttrEvalException("Cannot use "+name+" with first character in upper case in this context");
		
		char firstChar = name.charAt(0);
		String nameWithoutFirstChar = name.substring(1);
		String aux = new String(""+firstChar);
		aux = aux.toUpperCase();
		String Name = aux+nameWithoutFirstChar;
		
		//System.out.println(Name);
		
		return uf.findAssociationClassByName(Name);
	}
	
	/* * * * * * * * * * * * * * * *
	 * * * * * OTHER METHODS * * * *  
	 * * * * * * * * * * * * * * * */
	
	/**
	 * Gets the all variables
	 * @author Antonio Villegas
	 * @return the all variables
	 */
	public List getAllVariables()
	{
		List varList = new LinkedList();
		for (Iterator it = p.projecte.getOcl2().getVariable().refAllOfType().iterator(); it.hasNext();)
		{
			Variable v = (Variable) it.next();
			if (v.getName()!=null) {
				Variable vMem = new VariableImp(v);
				varList.add(vMem);
			}
		}
		return varList;
	}
	
	/**
	 * Find variable by name
	 * @author Antonio Villegas
	 * @param varName the name of the Variable
	 * 
	 * @return the variable
	 */
	public Variable findVariableByName(String varName)
	{
		Variable var = null;
		boolean found = false;
		List vars = getAllVariables();
		
		int i = 0;
		while (!found && i < vars.size())
		{
			Variable vaux = (Variable)vars.get(i);
			if (vaux.getName().equals(varName))
			{ 
				var = vaux;
				found = true;
			}
			else i++;
		}
		return var;
	}
	
	/**
	 * @author Antonio Villegas
	 * @param varName
	 * @param cls
	 * @return
	 */
	public Variable findVariableByNameAndClass(String varName, UmlClass cls)
	{
		Variable var = null;
		boolean found = false;
		List vars = getAllVariables();
		
		int i = 0;
		while (!found && i < vars.size())
		{
			Variable vaux = (Variable)vars.get(i);
			if (vaux.getName() != null && vaux.getType() != null) {
				if (vaux.getName().equals(varName) &&
						vaux.getType().equals(cls)	    )
				{ 
					var = vaux;
					found = true;
				}
			}	
			i++;
		}
		return var;
	}

	public PropertyCallExp createPCEforPropertyOfClass(OclExpression source, Property p) throws AttrEvalException {
		PropertyCallExp pce = (PropertyCallExp) factory.createNode("PropertyCallExp");
		
		if (!(source instanceof VariableExp)) {
			if (!(source instanceof PropertyCallExp)) {
				if (!(source instanceof AssociationClassCallExp)) {
					if (!(source instanceof OperationCallExp)) {
						if (!(source instanceof IteratorExp)) {
							if (!(source instanceof IterateExp)) {
								throw new AttrEvalException("Incorrect source for PropertyCallExp");
							}
						}
					}
				}
			}
		}
		
		// Set values
		pce.setSource(source);
		pce.setReferredProperty(p);
		pce.setType(p.getType());
	
		return pce;
	}
	
	public AssociationClassCallExp createACCEforNavigation(OclExpression source, AssociationClass ac) throws AttrEvalException {
		AssociationClassCallExp acce = (AssociationClassCallExp) factory.createNode("AssociationClassCallExp");
		
		if (!(source instanceof VariableExp)) {
			if (!(source instanceof PropertyCallExp)) {
				if (!(source instanceof AssociationClassCallExp)) {
					if (!(source instanceof OperationCallExp)) {
						throw new AttrEvalException("Incorrect source for AssociationClassCallExp");	
					}
				}
			}
		}
		
		// Set values
		acce.setSource(source);
		acce.setReferredAssociationClass(ac);
		
		// AssociationClassCallExp's type
		if (source instanceof PropertyCallExp 
			|| source instanceof AssociationClassCallExp 
			|| source instanceof OperationCallExp
			|| source instanceof IteratorExp
			|| source instanceof IterateExp) {
			int type = computeResultTypeOfNavigation(acce);
			if (type == this.SET_INDIVIDUAL) {
				acce.setType(ac);
			} else if (type == this.SET_MULTIPLE) {
				SetType st = (SetType) factory.createNode("SetType");
				st.setElementType(ac);
				st.setName("Set("+ac.getName()+")");
				acce.setType(st);
			} else if (type == this.BAG) {
				BagType bt = (BagType) factory.createNode("BagType");
				bt.setElementType(ac);
				bt.setName("Bag("+ac.getName()+")");
				acce.setType(bt);
			} else if (type == this.ORDEREDSET) {
				OrderedSetType ost = (OrderedSetType) factory.createNode("OrderedSetType");
				ost.setElementType(ac);
				ost.setName("OrderedSet("+ac.getName()+")");
				acce.setType(ost);
			} else if (type == this.SEQUENCE) {
				SequenceType st = (SequenceType) factory.createNode("SequenceType");
				st.setElementType(ac);
				st.setName("Sequence("+ac.getName()+")");
				acce.setType(st);
			} else throw new AttrEvalException("Incorrect Type for AssociationClassCallExp. This is a bug!");
		} else {
			// default = SetType
			SetType st = (SetType) factory.createNode("SetType");
			st.setElementType(ac);
			st.setName("Set("+ac.getName()+")");
			acce.setType(st);
		}
		
		return acce;
	}
	
	
	public int computeResultTypeOfNavigation(OclExpression exp) throws AttrEvalException {
		//========= PROPERTY CALL EXPRESSION ===========
		if (exp instanceof PropertyCallExp) {
			PropertyCallExp pce = (PropertyCallExp) exp;
			// Recursive Call
			int sourceType = computeResultTypeOfNavigation(pce.getSource());
			if (sourceType == ASSOCIATIVE_CLASS) {
				// referred property can be association end or attribute
				if (propertyIsAssociationEnd(pce.getReferredProperty())) {
					return SET_INDIVIDUAL;
				}
				else return ATTRIBUTE;
			}
			else if (sourceType == CLASS) {
				// referred property can be association end or attribute
				if (propertyIsAssociationEnd(pce.getReferredProperty())) {
					Property prop = pce.getReferredProperty();
					if (prop.getUpperValue()==null) throw new AttrEvalException("At "+prop.getName()+" cannot find upper cardinality");
					LiteralInteger li = (LiteralInteger) prop.getUpperValue();
					int maxcardinality = li.getValue();
					if (maxcardinality == -1) return SET_MULTIPLE;
					else if (maxcardinality == 1) return SET_INDIVIDUAL;
					else return SET_MULTIPLE; //by default
				}
				else return ATTRIBUTE;
			}
			else if (sourceType == SET_INDIVIDUAL) {
				// referred property can be association end or attribute
				if (propertyIsAssociationEnd(pce.getReferredProperty())) {
					Property prop = pce.getReferredProperty();
					// If I am a Set Individual, I am a Class or an AssociationClass.
					// So, if my referred property is a member of an association with AssociationClass
					// it indicates that I am an AssociationClass. It's impossible that I am a Class because
					// if the property is member of an association with AssociationClass, I must access to it firstly.
					if (prop.getAssociation() instanceof AssociationClass) {
						return SET_INDIVIDUAL;
					}
					if (prop.getUpperValue()==null) throw new AttrEvalException("At "+prop.getName()+" cannot find upper cardinality");
					LiteralInteger li = (LiteralInteger) prop.getUpperValue();
					int maxcardinality = li.getValue();
					if (maxcardinality == -1) return SET_MULTIPLE;
					else if (maxcardinality == 1) return SET_INDIVIDUAL;
					else return SET_MULTIPLE; //by default
				}
				else return ATTRIBUTE;
			}
			else if (sourceType == SET_MULTIPLE) {
				return BAG;
			}
			else if (sourceType == BAG) {
				return BAG;
			}
			else if (sourceType == ATTRIBUTE) {
				return ERROR;
			}
		}
		//===== ASSOCIATION CLASS CALL EXPRESSION =======
		else if (exp instanceof AssociationClassCallExp) {
			AssociationClassCallExp acce = (AssociationClassCallExp) exp;
			// Recursive Call
			int sourceType = computeResultTypeOfNavigation(acce.getSource());
			if (sourceType == ASSOCIATIVE_CLASS || sourceType == CLASS) {
				AssociationClass ac = acce.getReferredAssociationClass();
				List members = ac.getMemberEnd();
				if (members.size()==2) {
					Property m1 = (Property) members.get(0);
					Property m2 = (Property) members.get(1);
					int maxvalue1 = ((LiteralInteger)m1.getUpperValue()).getValue();
					int maxvalue2 = ((LiteralInteger)m2.getUpperValue()).getValue();
					if (maxvalue1 == 1 && maxvalue2 == 1) return SET_INDIVIDUAL;
					else return SET_MULTIPLE;
				}
				else return SET_MULTIPLE;
			}
			else if (sourceType == SET_INDIVIDUAL) {
				return SET_MULTIPLE;
			}
			else if (sourceType == SET_MULTIPLE) {
				return SET_MULTIPLE;
			}
			else if (sourceType == BAG) {
				return BAG;
			}
			else if (sourceType == ATTRIBUTE) {
				return ATTRIBUTE;
			}
			else return ERROR;
		}
		//========= TYPE OF EXP IS TUPLETYPE ============
		else if (exp.getType() instanceof TupleType) {
			return CLASS;
		}
		//============ VARIABLE EXPRESSION ==============
		else if (exp instanceof VariableExp) {
			VariableExp ve = (VariableExp) exp;
			if (ve.getType() instanceof AssociationClass) return ASSOCIATIVE_CLASS;
			else if(ve.getType() instanceof UmlClass) return CLASS;
			else {
				if (verbose) System.out.println(ve.getType().getName()+"VE Error");
				return ERROR;
			}
		}
		//============= OPERATION CALL EXPRESSION ========
		else if (exp instanceof OperationCallExp) {
			OperationCallExp oce = (OperationCallExp) exp;
			Type ocet = oce.getType();
			if (ocet instanceof AssociationClass) return ASSOCIATIVE_CLASS;
			else if (ocet instanceof UmlClass) return CLASS;
			else if (ocet instanceof CollectionType) {
				if (ocet instanceof SetType) return SET_MULTIPLE;
				else if (ocet instanceof BagType) return BAG;
			}
			
		}
		//============= ITERATOR EXPRESSION ========
		else if (exp instanceof IteratorExp) {
			IteratorExp ie = (IteratorExp) exp;
			Type iet = ie.getType();
			if (iet instanceof AssociationClass) return ASSOCIATIVE_CLASS;
			else if (iet instanceof UmlClass) return CLASS;
			else if (iet instanceof CollectionType) {
				if (iet instanceof SetType) return SET_MULTIPLE;
				else if (iet instanceof BagType) return BAG;
			}	
		}
		//============= ITERATE EXPRESSION ========
		else if (exp instanceof IterateExp) {
			IteratorExp ie = (IteratorExp) exp;
			Type iet = ie.getType();
			if (iet instanceof AssociationClass) return ASSOCIATIVE_CLASS;
			else if (iet instanceof UmlClass) return CLASS;
			else if (iet instanceof CollectionType) {
				if (iet instanceof SetType) return SET_MULTIPLE;
				else if (iet instanceof BagType) return BAG;
			}	
		}
		
		// ADD MORE CASE STUDY HERE
		//=== NEW SOURCE CASE ===
		// else if (exp instanceof ...) {
		//   ...
		// }
		throw new AttrEvalException("Error in computeResultTypeOfNavigation (Utility.java)");
	}
	
	public String getNameOfResultTypeOfNavigation(PropertyCallExp pce) throws AttrEvalException {
		int result = computeResultTypeOfNavigation(pce);
		String typename = pce.getReferredProperty().getType().getName();
		if (result == BAG) {
			return "Bag("+typename+")";
		} else if (result == SET_INDIVIDUAL) {
			return typename;
		} else if (result == SET_MULTIPLE) {
			return "Set("+typename+")";
		} else if (result == ATTRIBUTE) {
			return typename;
		} else return "Error";
	}
	
	public VariableExp obtainVariableExpForSelf(Environment env) {
		NamedElement ne = env.lookup("self"); 
		Variable v = (Variable) ne.getReferredElement();
		VariableExp ve = (VariableExp) factory.createNode("VariableExp");
		ve.setReferredVariable(v);
		ve.setType(v.getType());
		return ve;
	}

	public Type createTypeForCollectionPCE(int resulttype, Type type) {
		if (resulttype == SET_MULTIPLE || resulttype == SET_INDIVIDUAL) {
			SetType st = (SetType) factory.createNode("SetType");
			st.setElementType(type);
			st.setName("Set("+type.getName()+")");
			return st;
		} else if (resulttype == BAG) {
			BagType bt = (BagType) factory.createNode("BagType");
			bt.setElementType(type);
			bt.setName("Bag("+type.getName()+")");
			return bt;
		} else if (resulttype == SEQUENCE) {
			SequenceType st = (SequenceType) factory.createNode("SequenceType");
			st.setElementType(type);
			st.setName("Sequence("+type.getName()+")");
			return st;
		} else if (resulttype == ORDEREDSET) {
			OrderedSetType ost = (OrderedSetType) factory.createNode("OrderedSetType");
			ost.setElementType(type);
			ost.setName("OrderedSet("+type.getName()+")");
			return ost;
		}
		return null;
	}
	
	public Type createTypeForPCE(PropertyCallExp pce) throws AttrEvalException {
		if (this.hasCollectionType(pce)) {
			return this.createTypeForCollectionPCE(this.computeResultTypeOfNavigation(pce), pce.getType());
		}
		else return pce.getType();
	}
	
	public String convertToUpper(String name) {
		char firstChar = name.charAt(0);
		String nameWithoutFirstChar = name.substring(1);
		String aux = new String(""+firstChar);
		aux = aux.toUpperCase();
		return aux+nameWithoutFirstChar;
	}
	
	public String convertToLower(String name) {
		char firstChar = name.charAt(0);
		String nameWithoutFirstChar = name.substring(1);
		String aux = new String(""+firstChar);
		aux = aux.toLowerCase();
		return aux+nameWithoutFirstChar;
	}

	
	public Type newCollectionType(CollectionType ct, Type element) throws AttrEvalException {
		CollectionType c = null;
		if (ct instanceof BagType) {
			c = (BagType) factory.createNode("BagType");
			if (element != null) {
				c.setElementType(element);
				c.setName("Bag("+element.getName()+")");
			} else {
				c.setName("Bag()");
			}
		}
		else if (ct instanceof SetType) {
			c = (SetType) factory.createNode("SetType");
			if (element != null) {
				c.setElementType(element);
				c.setName("Set("+element.getName()+")");
			} else {
				c.setName("Set()");
			}
		}
		else if (ct instanceof OrderedSetType) {
			c = (OrderedSetType) factory.createNode("OrderedSetType");
			if (element != null) {
				c.setElementType(element);
				c.setName("OrderedSet("+element.getName()+")");
			} else {
				c.setName("OrderedSet()");
			}
		}
		else if (ct instanceof SequenceType) {
			c = (SequenceType) factory.createNode("SequenceType");
			if (element != null) {
				c.setElementType(element);
				c.setName("Sequence("+element.getName()+")");
			} else {
				c.setName("Sequence()");
			}
		}
		else throw new AttrEvalException("No matching collection type at newCollectionType(Utility.java)");
		return c;
	}
	
	public Type newCollectionType(String ct, Type element) throws AttrEvalException {
		CollectionType c;
		if (ct.equals("Bag")) {
			c = (BagType) factory.createNode("BagType");
			if (element != null) {
				c.setElementType(element);
				c.setName("Bag("+element.getName()+")");
			} else {
				c.setName("Bag()");
			}
		}
		else if (ct.equals("Set")) {
			c = (SetType) factory.createNode("SetType");
			if (element != null) {
				c.setName("Set("+element.getName()+")");
				c.setElementType(element);
			} else {
				c.setName("Set()");
			}
		}
		else if (ct.equals("OrderedSet")) {
			c = (OrderedSetType) factory.createNode("OrderedSetType");
			if (element != null) {
				c.setName("OrderedSet("+element.getName()+")");
				c.setElementType(element);
			} else {
				c.setName("OrderedSet()");
			}
		}
		else if (ct.equals("Sequence")) {
			c = (SequenceType) factory.createNode("SequenceType");
			if (element != null) {
				c.setName("Sequence("+element.getName()+")");
				c.setElementType(element);
			} else {
				c.setName("Sequence()");
			}
		}
		else throw new AttrEvalException("No matching collection type at newCollectionType(Utility.java)");
		return c;
	}

	public boolean hasCollectionType(PropertyCallExp source) throws AttrEvalException {
		int result = this.computeResultTypeOfNavigation(source);
		//System.out.println("Result "+ result);
		if (result == this.BAG || result == this.SET_INDIVIDUAL|| result == this.SET_MULTIPLE || result == this.ORDEREDSET || result == this.SEQUENCE) return true;
		return false;
	}

	public Type newCollectionType(PropertyCallExp source, Type element) throws AttrEvalException {
		int result = this.computeResultTypeOfNavigation(source);
		CollectionType c = null;
		if (result == this.BAG) {
			c = (BagType) factory.createNode("BagType");
			if (element != null) {
				c.setElementType(element);
				c.setName("Bag("+element.getName()+")");
			} else {
				c.setName("Bag()");
			}
		}
		else if (result == this.SET_MULTIPLE){
			c = (SetType) factory.createNode("SetType");
			if (element != null) {
				c.setElementType(element);
				c.setName("Set("+element.getName()+")");
			} else {
				c.setName("Set()");
			}
		}
		return c;
	}

	
	public boolean haveSameCollectionKind(Type t1, Type t2) {
		if (t1 instanceof SetType && t2 instanceof SetType) return true;
		if (t1 instanceof OrderedSetType && t2 instanceof OrderedSetType) return true;
		if (t1 instanceof BagType && t2 instanceof BagType) return true;
		if (t1 instanceof SequenceType && t2 instanceof SequenceType) return true;
		return false;
	}

	public boolean isSubClass(UmlClass superclass, UmlClass subclass) throws AttrEvalException {
		UmlClass fatherofsubclass = this.getSuperClass(subclass);
		if (fatherofsubclass != null) {
			if (superclass.equals(fatherofsubclass)) return true;
			else return  isSubClass(superclass, fatherofsubclass);
		}
		else return false;
	}

	public boolean isFirstCharUpper(String name) {
		String c = name.substring(0, 1);
		String d = new String(c);
		if (d.toUpperCase().equals(c)) {
			return true;
		}
		return false;
	}

	
	
	public void insertQualifier(AssociationClassCallExp acce, String qualifier) throws AttrEvalException {
		AssociationClass ac = acce.getReferredAssociationClass();
		List members = ac.getMemberEnd();
		Property pquali = null;
		for (Iterator iterator = members.iterator(); iterator.hasNext();) {
			Property p = (Property) iterator.next();
			if (p.getName().equals(qualifier)) {
				pquali = p;
			}
		}
		if (pquali == null) throw new AttrEvalException("Qualifier "+qualifier+" is not a correct qualifier for "+ac.getName());
		
		PropertyCallExp pce = (PropertyCallExp) this.factory.createNode("PropertyCallExp");
		pce.setReferredProperty(pquali);
		pce.setName(qualifier);
		pce.setType(pquali.getType());
		
		AQualifierParentNavFacade aqpnf = new AQualifierParentNavFacade(this.p);
		AQualifierParentNav aqpn = aqpnf.getAQualifierParentNav();
		aqpn.add(pce, acce);
	}

	
	public void throwHelpOfCorrectCallWithQualifier(AssociationClass ac) throws AttrEvalException {
		//check if the association class needs to be used with a qualifier construction
		if (this.associationClassNeedsQualifier(ac)) {
			Property m1 = (Property) ac.getMemberEnd().get(0);
			String s1 = m1.getName();
			if (s1.equals("")) s1 = this.convertToLower(m1.getType().getName());
		
			Property m2 = (Property) ac.getMemberEnd().get(1);
			String s2 = m2.getName();
			if (s2.equals("")) s2 = this.convertToLower(m2.getType().getName());
			
			String acname = this.convertToLower(ac.getName());
			throw new AttrEvalException("Access Error: "+ac.getName()+" can only be accesed " +
						"with a qualifier:\n\t"+acname+"["+s1+"] or "+acname+"["+s2+"]");
		}
	}
	
	public boolean associationClassNeedsQualifier(AssociationClass ac) {
		if (ac.getMemberEnd().size()==2) {
			Property p1 = (Property) ac.getMemberEnd().get(0);
			Property p2 = (Property) ac.getMemberEnd().get(1);
			if (p1.getType().equals(p2.getType())) return true;
		}
		return false;
	}

	public void allVariablesUsed(IteratorExp myAst) throws AttrEvalException {
		Collection iters = myAst.getIterator();
		for (Iterator iterator = iters.iterator(); iterator.hasNext();) {
			Variable itvar = (Variable) iterator.next();
			if (!itvar.getName().equals("")) {
				if(!isUsed(itvar, myAst.getBody())) {
					throw new AttrEvalException("Incorrect usage: "+itvar.getName()+
							" not used inside "+myAst.getName());
				}
			}
		}
	}

	private boolean isUsed(Variable itvar, OclExpression body) {
		if (body instanceof OperationCallExp) {
			Collection params = ((OperationCallExp)body).getArgument();
			for (Iterator iterator = params.iterator(); iterator.hasNext();) {
				OclExpression param = (OclExpression) iterator.next();
				if (isUsed(itvar, param)) return true;
			}
		}
		else if (body instanceof VariableExp) {
			Variable v = ((VariableExp)body).getReferredVariable();
			if (v.getName().equals(itvar.getName()) && v.getType().equals(itvar.getType())) {
				return true;
			}
		}
		else if (body instanceof TupleLiteralExp){
			TupleLiteralExp tbody = (TupleLiteralExp) body;
			Collection parts = tbody.getPart();
			for (Iterator iterator = parts.iterator(); iterator.hasNext();) {
				TupleLiteralPart tlp = (TupleLiteralPart) iterator.next();
				if (isUsed(itvar,tlp.getValue())) return true;
			}
		}
		else if (body instanceof IteratorExp) {
			IteratorExp ie = (IteratorExp) body;
			if (isUsed(itvar, ie.getBody())) return true;
		}
		
		else if (body instanceof IterateExp) {
			IterateExp ie = (IterateExp) body;
			if (isUsed(itvar, ie.getBody())) return true;
		}
		
		if (body instanceof CallExp) {
			if (((CallExp)body).getSource()!=null) return isUsed(itvar, ((CallExp)body).getSource());
		}
		
		return false;
	}

	
	public Property createPropertyFromInitVariable(Variable v) {
		OclExpression init = v.getInitExpression();
		Property p = (Property)this.factory.createNode("Property");
		p.setName(v.getName());
		p.setType(v.getType());
		
		return p;
	}
	
	public Type getCorrectType(TypedElement te) throws AttrEvalException {
		if (te instanceof PropertyCallExp) {
			if (hasCollectionType((PropertyCallExp) te)) {
				return createTypeForPCE((PropertyCallExp) te);
			}
		}	
		if (te.getType() == null) return tc.typecheck(te);
		else return te.getType();
	}

	
	public Operation lookupOperationWithOCLFormalParameter(UmlClass ctxCls, String operName, List params) {
		List opers = ctxCls.getOwnedOperation();
		Operation returnop = null;
		boolean found = false;
		for (Iterator iterator = opers.iterator(); iterator.hasNext() && !found;) {
			Operation ownop = (Operation) iterator.next();
			if (ownop.getName().equals(operName)) {
				// preprocess the params list in order to extract the return parameter
				// because getType is not accessible and the return type is made with a return parameter
				List ownpars = ownop.getOwnedParameter();
				LinkedList paramsNoReturn = new LinkedList();
				for (Iterator iterator2 = ownpars.iterator(); iterator2.hasNext();) {
					Parameter par = (Parameter) iterator2.next();
					if (!par.getDirection().equals(ParameterDirectionKindEnum.RETURN))
						paramsNoReturn.add(par);
				}
				// compare parameters
				if (paramsNoReturn.size() == params.size()) {
					int i = 0;
					boolean OK = true;
					for (Iterator iterator3 = paramsNoReturn.iterator(); iterator3.hasNext() && OK;) {
						Parameter ownpar = (Parameter) iterator3.next();
						OclFormalParameter par = (OclFormalParameter) params.get(i);
						if (ownpar.getName().equals(par.getName())) {
							if (!ownpar.getType().getName().equals(par.getType().getName())) {
								OK = false;
							}
						}
						else OK = false;
						++i;
					}
					if (i == paramsNoReturn.size()){
						found = true;
						returnop = ownop;
					}
				}
			}
			
		}// endfor

		return returnop;
	}
	
	
	
	// params List are OCLExpressions
	public Operation lookupOperationWithRealParams(UmlClass ctxCls, String operName, List params) throws AttrEvalException {
		List opers = ctxCls.getOwnedOperation();
		Operation returnop = null;
		boolean found = false;
		for (Iterator iterator = opers.iterator(); iterator.hasNext() && !found;) {
			Operation ownop = (Operation) iterator.next();
			if (ownop.getName().equals(operName)) {
				// preprocess the params list in order to extract the return parameter
				// because getType is not accessible and the return type is made with a return parameter
				List ownpars = ownop.getOwnedParameter();
				LinkedList paramsNoReturn = new LinkedList();
				for (Iterator iterator2 = ownpars.iterator(); iterator2.hasNext();) {
					Parameter par = (Parameter) iterator2.next();
					if (!par.getDirection().equals(ParameterDirectionKindEnum.RETURN))
						paramsNoReturn.add(par);
				}
				// compare parameters
				if (paramsNoReturn.size() == params.size()) {
					int i = 0;
					boolean OK = true;
					for (Iterator iterator3 = paramsNoReturn.iterator(); iterator3.hasNext() && OK;) {
						Parameter ownpar = (Parameter) iterator3.next();
						OclExpression par = (OclExpression) params.get(i);
						if (!ownpar.getType().getName().equals(this.getCorrectType(par).getName())) {
							OK = false;
						}
						else OK = false;
						++i;
					}
					if (i == paramsNoReturn.size()){
						found = true;
						returnop = ownop;
					}
				}
			}
			
		}// endfor

		return returnop;
	}

	
	
	public boolean connectionWithPCE(UmlClass source, AssociationClass ac) throws AttrEvalException {
		Collection elements = ac.getTypedElementOfType();
		for (Iterator it = elements.iterator(); it.hasNext();) {
			Element el = (Element) it.next();
			if (el instanceof Property) {
				Property pend = (Property) el;
				if (this.propertyIsAssociationEnd(pend)) {
					Association as = pend.getAssociation();
					List memb = as.getMemberEnd();
					for (Iterator it2 = memb.iterator(); it2.hasNext();) {
						Property pend2 = (Property) it2.next();
						if (pend2.getType().equals(source)) return true;
					}
				}
			}
		}
		
		// if we are here, we must try it again with the superclass of source
		UmlClass superClass = this.getSuperClass(source);
		if (superClass == null) { 
			return false;
		}
		else {
			if (verbose) System.out.println("superclass:"+superClass.getName());
			return existsConnection (superClass, ac);
		}
	}

	public Property getPropertyOfConnection(UmlClass source, AssociationClass ac) throws AttrEvalException {
		Collection elements = ac.getTypedElementOfType();
		for (Iterator it = elements.iterator(); it.hasNext();) {
			Element el = (Element) it.next();
			if (el instanceof Property) {
				Property pend = (Property) el;
				if (this.propertyIsAssociationEnd(pend)) {
					Association as = pend.getAssociation();
					List memb = as.getMemberEnd();
					for (Iterator it2 = memb.iterator(); it2.hasNext();) {
						Property pend2 = (Property) it2.next();
						if (pend2.getType().equals(source)) return pend;
					}
				}
			}
		}
		
		// if we are here, we must try it again with the superclass of source
		UmlClass superClass = this.getSuperClass(source);
		if (superClass == null) { 
			return null;
		}
		else {
			if (verbose) System.out.println("superclass:"+superClass.getName());
			return getPropertyOfConnection (superClass, ac);
		}
	}

	
	// Method to avoid navigations between two members of an association with
	// association class without passing through the association class itself
	public boolean checkCorrectNavigation(UmlClass source, Property prop) throws AttrEvalException {
		if (prop.getAssociation() instanceof AssociationClass) {
			AssociationClass ac = (AssociationClass) prop.getAssociation();
			if (ac.equals(source)) return true;
			else {
				// if we are here, we must try it again with the superclass of source
				UmlClass superClass = this.getSuperClass(source);
				if (superClass == null) { 
					throw new AttrEvalException("To navigate from "+source.getName()+" and "
							+prop.getType().getName()+" please go through Association Class "+ac.getName());
				}
				else return checkCorrectNavigationRec(superClass, prop, source.getName());
			}
		}
		else return true;
	}
	private boolean checkCorrectNavigationRec(UmlClass source, Property prop, String sourcename) throws AttrEvalException {
		if (prop.getAssociation() instanceof AssociationClass) {
			AssociationClass ac = (AssociationClass) prop.getAssociation();
			if (ac.equals(source)) return true;
			else {
				// if we are here, we must try it again with the superclass of source
				UmlClass superClass = this.getSuperClass(source);
				if (superClass == null) { 
					throw new AttrEvalException("To navigate from "+sourcename+" and "
							+prop.getType().getName()+" please go through Association Class "+ac.getName());
				}
				else return checkCorrectNavigationRec(superClass, prop, sourcename);
			}
		}
		else return true;
	}



	

	

}
